﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows;
using System.Linq;
using System.Windows.Interop;
using Garfield.Core.Runtime;

namespace Garfield.Presentation.View
{
    public class WindowNavigator : INavigator
    {
        private readonly IExtensionPoint<string, UIElement> viewParts;
        private readonly IExtensionPoint<string, Window> chromes;
        private readonly IDictionary<ViewKey, UIElement> openedViews = new Dictionary<ViewKey, UIElement>();

        public WindowNavigator([ExtensionPoint(PresentationPlugin.VIEW_PARTS)]IExtensionPoint<string, UIElement> viewParts,
            [ExtensionPoint(PresentationPlugin.WINDOW_CHROMES)] IExtensionPoint<string, Window> chromes)
        {
            this.viewParts = viewParts;
            this.chromes = chromes;
        }

        public string DefaultChrome { get; set; }

        public void NavigateToView(string chromeId, string viewPartId, object parameter)
        {
            AssumeViewPartRegistered(viewPartId);
            AssumeViewChromeRegistered(chromeId);

            var key = new ViewKey(chromeId, viewPartId, parameter);
            if (openedViews.ContainsKey(key))
            {
                openedViews[key].RaiseEvent(new RoutedEventArgs(ViewPart.BringToFrontEvent));
                return;
            }
            OpenView(key);
        }

        private void OpenView(ViewKey key)
        {
            var element = viewParts.GetExtension(key.ViewId);
            element.RaiseEvent(new ViewInitializedArgs(key.Parameter));
            var window = CreateView(key.ChromeId, element);
            window.Closing += (sender, e) => element.RaiseEvent(new RoutedEventArgs(ViewPart.ClosingEvent, e));
            window.Closed += (sender, e) => { openedViews.Remove(key);
                                                element.RaiseEvent(new RoutedEventArgs(ViewPart.ClosedEvent, e)); };       
            window.AddHandler(ViewPart.BringToFrontEvent, new RoutedEventHandler((sender, e) => {
                                                                                                 SetForegroundWindow(new WindowInteropHelper(window).Handle);
                                                                                                 window.Focus();}));            
            window.Show();
            openedViews[key] = element;
        }

        private void AssumeViewChromeRegistered(string chromeId)
        {
            if (!chromes.ExtensionIdentifiers.Contains(chromeId))
                throw new ViewChromeNotFoundException(chromeId);
        }

        private void AssumeViewPartRegistered(string viewPartId)
        {
            if (!viewParts.ExtensionIdentifiers.Contains(viewPartId))
                throw new ViewPartNotFoundException(viewPartId);
        }

        private Window CreateView(string chromeId, object viewData)
        {
            var window = chromes.GetExtension(chromeId);
            window.Content = viewData;
            return window;
        }

        public void NavigateToView(string viewPartId, object parameter)
        {
            NavigateToView(DefaultChrome, viewPartId, parameter);
        }

        [DllImport("user32")]
        internal static extern int SetForegroundWindow(IntPtr hwnd);
    }

    class ViewKey : IEquatable<ViewKey>
    {
        public string ChromeId { get; private set; }
        public string ViewId { get; private set; }
        public object Parameter { get; private set; }

        public ViewKey(string chromeId, string viewId, object parameter)
        {
            ChromeId = chromeId;
            ViewId = viewId;
            Parameter = parameter;
        }

        public bool Equals(ViewKey obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            return Equals(obj.ChromeId, ChromeId) && Equals(obj.ViewId, ViewId) && Equals(obj.Parameter, Parameter);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != typeof (ViewKey)) return false;
            return Equals((ViewKey) obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int result = (ChromeId != null ? ChromeId.GetHashCode() : 0);
                result = (result*397) ^ (ViewId != null ? ViewId.GetHashCode() : 0);
                result = (result*397) ^ (Parameter != null ? Parameter.GetHashCode() : 0);
                return result;
            }
        }
    }
}
